<!-- 3D地图组件 -->
<!-- 使用TresJS（Three.js的Vue封装库）实现3D地图可视化 -->
<template>
  <!-- TresGroup用于组织和管理3D对象 -->
  <TresGroup ref="tgRef">
    <!-- 遍历区域列表，根据类型渲染不同的3D元素 -->
    <template v-for="(item, index) in areaList" :key="`${index}`">
      <!-- 渲染地区名称文本标签 -->
      <Html
        v-if="item.type === 'Html'"
        v-bind="htmlState"
        :position="item.position"
      >
        <span>
          {{ item.name }}
        </span>
      </Html>

      <!-- 渲染地区图标（精灵图） -->
      <TresSprite
        v-if="item.type === 'Sprite'"
        :position="item.position"
        :scale="0.3"
        :renderOrder="1000"
      >
        <TresSpriteMaterial
          :color="0xff0000"
          :blending="THREE.NormalBlending"
          :map="pTexture"
        />
      </TresSprite>

      <!-- 渲染地区区域（3D挤压形状） -->
      <TresMesh
        v-if="item.type === 'Shape'"
        :name="item.name"
        :renderOrder="index"
        :pCenter="item.pCenter"
        @pointer-enter="pEnter"
        @pointer-leave="pLeave"
        @click="pClick"
      >
        <TresExtrudeGeometry
          :args="[item.shape, { depth: item.depth, bevelEnabled: false }]"
        />
        <TresMeshStandardMaterial
          :color="item.color"
          :emissive="0x000000"
          :roughness="0.45"
          :metalness="0.8"
          :transparent="true"
          :side="THREE.DoubleSide"
        />
      </TresMesh>

      <!-- 渲染地区边界线（双线效果：白色在上，黑色在下） -->
      <!-- <template v-if="item.type === 'Line'"> -->
      <!-- 上层白色线条 -->
      <!-- <TresLine :renderOrder="index" :position-z="item.depth + 0.0001">
          <TresBufferGeometry :position="[item.points, 3]" />
          <TresLineBasicMaterial :color="0xffffff" :linewidth="0.5" />
        </TresLine> -->
      <!-- 下层黑色线条（产生描边效果） -->
      <!-- <TresLine :renderOrder="index" :position-z="-0.0001">
          <TresBufferGeometry :position="[item.points, 3]" />
          <TresLineBasicMaterial :color="0x000000" :linewidth="0.5" />
        </TresLine>
      </template> -->
    </template>
  </TresGroup>
</template>

<script setup>
// 导入必要的库和工具函数
import { watchEffect, ref } from "vue"; // Vue3 组合式API
import * as D3 from "d3-geo"; // 地理数据处理库
import * as THREE from "three"; // 3D渲染库
import { loadGeojson } from "@/components/three/common/request"; // 加载GeoJSON数据的工具函数
import { Html } from "@tresjs/cientos"; // TresJS扩展组件
import { useTexture, useTresContext, useRenderLoop } from "@tresjs/core"; // TresJS核心功能
import {
  computeBoundsTree,
  disposeBoundsTree,
  acceleratedRaycast,
} from "three-mesh-bvh"; // 用于优化射线检测的库
import { flyTo } from "@/components/three/common/utils"; // 相机飞行动画工具函数

// 初始化three-mesh-bvh，用于加速射线检测（优化性能）
const initMeshBvh = () => {
  THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
  THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
  THREE.Mesh.prototype.raycast = acceleratedRaycast;
};
initMeshBvh();

// 加载江苏省GeoJSON地图数据
const areaJson = await loadGeojson(
  "src/components/three/json/32000_full.json",
  "features"
);

// 加载图标纹理
const { map: pTexture } = await useTexture({
  map: "src/components/three/image/icon.png",
});

// 获取地图中心点
const center = areaJson[0].properties.centroid;

// 创建D3墨卡托投影，用于将地理坐标转换为平面坐标
const offsetXY = D3.geoMercator();
offsetXY.center(center).translate([0, 0]); // 设置投影中心和偏移

// 存储处理后的区域数据
const areaList = [];

// 处理地图数据，创建3D表示
const makeAreaPrimary = () => {
  areaJson.forEach((feature) => {
    // 为每个区域随机生成HSL颜色（保持色调一致但亮度和饱和度有变化）
    const color = new THREE.Color(
      `hsl( ${16}, ${Math.random() * 30 + 55}%, ${Math.random() * 30 + 55}%)`
    ).getHex();

    // 随机生成区域高度
    const depth = Math.random() * 0.3 + 0.3;

    // 提取区域属性
    const { centroid, oneCenter, center: Cc, name } = feature.properties;
    const { coordinates, type } = feature.geometry;
    // 获取区域中心点（尝试多个属性，确保能获取到中心点）
    const point = centroid || oneCenter || Cc || [0, 0];

    // 计算HTML标签位置
    const htmlPosition = offsetXY(point);
    htmlPosition[1] = -htmlPosition[1]; // Y轴反转
    htmlPosition[2] = depth; // 设置Z轴高度
    areaList.push({ type: "Html", position: htmlPosition, name });

    // 计算精灵图标位置
    const spritePosition = offsetXY(point);
    spritePosition[1] = -spritePosition[1] + 0.2; // Y轴反转并略微上移
    spritePosition[2] = depth + 0.22; // 比区域稍高的Z轴高度
    areaList.push({ type: "Sprite", position: spritePosition });

    // 处理区域坐标数据
    coordinates.forEach((coordinate) => {
      // 处理不同类型的多边形数据
      function fn(crdinate) {
        // 创建区域形状
        const shape = new THREE.Shape();
        crdinate.forEach((item, idx) => {
          const [x, y] = offsetXY(item);
          // 第一个点使用moveTo，后续点使用lineTo
          if (idx === 0) shape.moveTo(x, -y);
          else shape.lineTo(x, -y);
        });

        // 添加区域形状到列表
        areaList.push({
          type: "Shape",
          shape,
          name,
          color,
          depth,
          pCenter: spritePosition,
        });

        // 创建边界线
        const points = [];
        crdinate.forEach((item) => {
          const [x, y] = offsetXY(item);
          points.push(x, -y, 0);
        });

        // 添加边界线到列表
        areaList.push({
          type: "Line",
          points: new Float32Array(points),
          depth,
        });
      }

      // 根据几何类型处理坐标
      if (type === "MultiPolygon") coordinate.forEach((item) => fn(item));
      if (type === "Polygon") fn(coordinate);
    });
  });
};
makeAreaPrimary(); // 执行数据处理

// 设置地图居中
const setCenter = (map) => {
  map.rotation.x = -Math.PI / 2; // 绕X轴旋转90度，使地图水平展示
  // 创建包围盒并计算中心点
  const box = new THREE.Box3().setFromObject(map);
  const centerMap = box.getCenter(new THREE.Vector3());

  const offset = [0, 0]; // 可以设置额外偏移量
  // 调整地图位置使其居中
  map.position.x = map.position.x - centerMap.x - offset[0];
  map.position.z = map.position.z - centerMap.z - offset[1];
};

// 引用TresGroup组件
const tgRef = ref();

// 监听组件挂载，设置地图居中并计算包围树
watchEffect(() => {
  if (tgRef.value) {
    setCenter(tgRef.value);
    // 为所有网格计算包围树，优化射线检测性能
    tgRef.value.children.forEach((item) => {
      if (item.type === "Mesh") {
        item.geometry.computeBoundsTree();
      }
    });
  }
});

// 鼠标悬停进入区域时，降低不透明度（高亮效果）
const pEnter = (intersection) => {
  intersection.object.material.opacity = 0.4;
};

// 鼠标悬停离开区域时，恢复不透明度
const pLeave = (intersection) => {
  intersection.eventObject.material.opacity = 1;
};

// 获取场景相机和控制器
const { camera, controls } = useTresContext();

// 存储动画实例
let twInstant = null;

// 鼠标点击区域时，相机飞行到该位置
const pClick = (intersection) => {
  const targetPosition = new THREE.Vector3();
  targetPosition.x = intersection.point.x;
  targetPosition.y = intersection.point.y + 10; // 相机位置比交点高10个单位
  targetPosition.z = intersection.point.z;
  // 执行相机飞行动画
  twInstant = flyTo(camera, targetPosition, controls);
};

// 获取渲染循环钩子
const { onBeforeLoop } = useRenderLoop();

// 在每帧渲染前更新动画（如果存在）
onBeforeLoop(() => {
  twInstant?.update();
});

// HTML标签配置
const htmlState = {
  wrapperClass: "wrapper",
  as: "div",
  center: true,
  sprite: true,
  prepend: true,
  transform: true,
};
</script>

<style lang="scss" scoped>
/* 文本标签样式 */
.wrapper {
  #inner {
    user-select: none; // 禁止文本选择
    pointer-events: none !important; // 禁止鼠标事件，使文本标签不会捕获鼠标事件

    span {
      text-shadow: 1px 1px 2px #c92704; // 文本阴影效果，增强可读性
      color: #fff; // 文本颜色为白色
      font-size: 12px; // 文本大小
    }
  }
}
</style>
